#include "callother.h"
#include "compiler_unit.h"

static AstNode *g_node_list;
static int g_id;

#define VOID_EFUN_NEED_RETURN   1024

#define NODE_ID(x)  ((x)?(x)->id:-1)

void AstNode_Init(void)
{
    g_node_list = 0;
    g_id = 1;
}

void AstNode_FreeAllNodes(void)
{
    AstNode *node;

    while (g_node_list) {
        node = g_node_list;
        g_node_list = g_node_list->next;
        CO_FREE(node);
    }
}

int AstNode_IsNode(AstNode *self, enum AstNodeKind kind, int number)
{
    return self->kind == kind && self->v.number == number;
}

static AstNode *new_node()
{
    AstNode *node;
    node = (AstNode *) CO_MALLOC(sizeof(AstNode));
    node->id = g_id++;

    node->next = g_node_list;
    g_node_list = node;

    return node;
}

AstNode *AstNode_CreateReturn(AstNode *right)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] NODE_RETURN, r = [%d]", NODE_ID(node), NODE_ID(right));

    node->kind = NODE_RETURN;
    node->r.expr = right;

    return node;
}

AstNode *AstNode_CreateString(int index)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] NODE_STRING, v = %d", NODE_ID(node), index);

    node->kind = NODE_STRING;
    node->type = CTYPE_STRING;
    node->v.number = index;

    return node;
}

AstNode *AstNode_SetError(AstNode *node)
{
    debug(AST, "[%d] set error", node->id);

    node->kind = NODE_NUMBER;
    node->type = CTYPE_ANY;

    return node;
}

AstNode *AstNode_CreateError(void)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] NODE_NUMBER", node->id);

    return AstNode_SetError(node);
}

AstNode *AstNode_CreateOpCodeWithOneArg(int op, int type, int arg)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] NODE_OPCODE_1, type = %d, v = %d, l = %d", NODE_ID(node), type, op, arg);

    node->kind = NODE_OPCODE_1;
    node->type = type;
    node->v.number = op;
    node->l.number = arg;

    return node;
}

AstNode *AstNode_CreateBinaryOp(int op, int type, AstNode *left, AstNode *right)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] NODE_BINARY_OP, type = %d, v = %d, l = [%d], r = [%d]", NODE_ID(node), type, op, NODE_ID(left), NODE_ID(right));

    node->kind = NODE_BINARY_OP;
    node->type = type;
    node->v.number = op;
    node->l.expr = left;
    node->r.expr = right;

    return node;
}

AstNode *AstNode_CreateUnaryOp(int op, int type, AstNode *right)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] NODE_UNARY_OP, type = %d, v = %d, r = [%d]", NODE_ID(node), type, op, NODE_ID(right));

    node->kind = NODE_UNARY_OP;
    node->type = type;
    node->v.number = op;
    node->r.expr = right;

    return node;
}

AstNode *AstNode_CreateUnaryOpWithOneArg(int op, int type, AstNode *right, int arg)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] NODE_UNARY_OP_1, type = %d, v = %d, l = %d, r = [%d]", NODE_ID(node), type, op, arg, NODE_ID(right));

    node->kind = NODE_UNARY_OP_1;
    node->type = type;
    node->v.number = op;
    node->r.expr = right;
    node->l.number = arg;

    return node;
}

AstNode *AstNode_CreateTwoValues(int type, AstNode *left, AstNode *right)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] NODE_TWO_VALUES, type = %d, l = [%d], r = [%d]", NODE_ID(node), type, NODE_ID(left), NODE_ID(right));

    node->kind = NODE_TWO_VALUES;
    node->type = type;
    node->l.expr = left;
    node->r.expr = right;

    return node;
}

AstNode *AstNode_CreateExprList(AstNode *right)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] EXPR_LIST, r = [%d]", NODE_ID(node), NODE_ID(right));

    if (right) {
        node->v.number = right->kind;
        node->l.expr = right->l.expr;
    }
    else {
        node->v.number = 0;
        node->l.expr = node;
    }
    node->r.expr = right;

    return node;
}

AstNode *AstNode_CreateExprNode(AstNode *subnode)
{
    AstNode *node;
    node = new_node();

    debug(AST, "[%d] EXPR NODE, v = [%d]", NODE_ID(node), NODE_ID(subnode));

    node->kind = 1;
    node->v.expr = subnode;
    node->l.expr = node;
    node->r.expr = 0;

    return node;
}

AstNode *AstNode_ConnectExprNodes(AstNode *chain, AstNode *subnode)
{
    debug(AST, "[%d] connect expr nodes, subnode = [%d]", NODE_ID(chain), NODE_ID(subnode));

    subnode->kind = 0;

    chain->kind++;
    chain->l.expr->r.expr = subnode;
    chain->l.expr = subnode;

    return chain;
}

AstNode *AstNode_CallEFun(AstNode *node, int efun_id, int num_arg, int type)
{
    debug(AST, "[%d] call efun, efun = %d, num_arg = %d, type = %d", NODE_ID(node), efun_id, num_arg, type);

    node->kind = NODE_EFUN;
    node->type = type;
    node->v.number = efun_id;
    node->l.number = num_arg;

    /* NOTE: efun with return type void won't push any value.
       The flag is used to tell the parser to append a push instruction manually when generating code.
       It may be turned off during insert_pop_value.
       */
    if (node->type == CTYPE_VOID) {
        node->v.number |= VOID_EFUN_NEED_RETURN;
    }

    return node;
}

AstNode *AstNode_CallFunction(AstNode *node, int index, int type)
{
    debug(AST, "[%d] call function, index = %d, type = %d", NODE_ID(node), index, type);

    node->kind = NODE_CALL_1;
    node->type = type;
    node->v.number = F_CALL_FUNCTION_BY_ADDRESS;
    node->l.number = index;

    return node;
}

AstNode *insert_pop_value(AstNode *node)
{
    if (!node) {
        return 0;
    }
    switch (node->kind) {
        case NODE_EFUN:
            {
                /* no need to push a value for return */
                if (node->v.number & VOID_EFUN_NEED_RETURN) {
                    node->v.number &= ~VOID_EFUN_NEED_RETURN;
                    return node;
                }
                break;
            }
        case NODE_TWO_VALUES:
            {
                /* (two-values expr1 expr2) where expr1 is already popped.
                 *
                 * instead of: (pop (two-values expr1 expr2))
                 * generated:  (two-values node (pop expr2))
                 *
                 * both of which generate the same code, but the second optimizes
                 * better in cases like: i++, j++
                 *
                 * we get: (two-values (inc i) (post-inc j))
                 * first: (pop (two-values (inc i) (post-inc j)))
                 * -> INC i; POST_INC j; POP
                 * second: (two-values (inc i) (inc j))
                 * -> INC i; INC j
                 */
                if ((node->r.expr = insert_pop_value(node->r.expr)))
                    return node;
                return node->l.expr;
            }
        case NODE_BINARY_OP:
        case NODE_UNARY_OP_1:
        case NODE_UNARY_OP:
        case NODE_OPCODE_1:
            {
                switch (node->v.number) {
                    case F_LOCAL:
                        {
                            return 0;
                        }
                    case F_ADD:
                        {
                            if ((node->l.expr = insert_pop_value(node->l.expr))) {
                                if ((node->r.expr = insert_pop_value(node->r.expr))) {
                                    node->kind = NODE_TWO_VALUES;
                                    return node;
                                } else {
                                    return node->l.expr;
                                }
                            } else {
                                return insert_pop_value(node->r.expr);
                            }
                            break;
                        }
                    case F_ASSIGN:
                        {
                            if (AstNode_IsNode(node->r.expr, NODE_OPCODE_1, F_LOCAL_LVALUE)) {
                                int tmp = node->r.expr->l.number;
                                node->kind = NODE_UNARY_OP_1;
                                node->r.expr = node->l.expr;
                                node->v.number = F_VOID_ASSIGN_LOCAL;
                                node->l.number = tmp;
                            } else {
                                node->v.number = F_VOID_ASSIGN;
                            }
                            return node;
                        }
                }
                break;
            }
        case NODE_NUMBER:
        case NODE_STRING:
            return 0;
    }

    return AstNode_CreateUnaryOp(F_POP_VALUE, 0, node);
}

AstNode *AstNode_PopValue(AstNode *self)
{
    if (self) {
        AstNode *poped = insert_pop_value(self);

        if (!poped) {
            /* TODO: Value of conditional expression is unused
            yywarn("Expression has no side effects, and the value is unused");
            */
        }
        return poped;
    }
    return 0;
}

#define DEFAULT_CODE_SIZE   4096

static void realloc_code(Code *code)
{
    int oldsize;
    int newsize;

    oldsize = code->c_end - code->c_start;
    newsize = (code->c_max - code->c_start) << 1;
    code->c_start = CO_REALLOC(code->c_start, newsize);
    code->c_end = code->c_start + oldsize;
    code->c_max = code->c_start + newsize;
}

static void ins_byte(Code *code, unsigned char b)
{
    if (code->c_end == code->c_max) {
        realloc_code(code);
    }
    *code->c_end = b;
    code->c_end++;
}

/*
 * Store a 2 byte number. It is stored in such a way as to be sure
 * that correct byte order is used, regardless of machine architecture.
 * Also beware that some machines can't write a word to odd addresses.
 */
static void ins_short(Code *code, short l)
{
    if (code->c_end + 2 == code->c_max) {
        realloc_code(code);
    }
    STORE_SHORT(code->c_end, l);
}

/*
 * Store a 4 byte number. It is stored in such a way as to be sure
 * that correct byte order is used, regardless of machine architecture.
 */
static void ins_int(Code *code, int l)
{
    if (code->c_end + 4 == code->c_max) {
        realloc_code(code);
    }
    STORE_INT(code->c_end, l);
}

static void generate_node(Code *code, AstNode *node);

static void generate_expr_list(Code *code, AstNode *node)
{
    debug(COMPILE, "generate expr list");

    if (!node) {
        return;
    }

    do {
        generate_node(code, node->v.expr);
        node = node->r.expr;
    } while (node);
}

static void generate_node(Code *code, AstNode *node)
{
    if (!node) {
        return;
    }

    switch (node->kind) {
        default:
            {
                /* TODO: fatal */
                printf("Unknown node %d in generate_node.\n", node->kind);
                break;
            }
        case NODE_BINARY_OP:
            {
                debug(COMPILE, "generate [%d] NODE_BINARY_OP", node->id);

                generate_node(code, node->l.expr);
                generate_node(code, node->r.expr);
                ins_byte(code, node->v.number);
                break;
            }
        case NODE_UNARY_OP:
            {
                debug(COMPILE, "generate [%d] NODE_UNARY_OP", node->id);

                generate_node(code, node->r.expr);
                ins_byte(code, node->v.number);
                break;
            }
        case NODE_UNARY_OP_1:
            {
                debug(COMPILE, "generate [%d] NODE_UNARY_OP_1", node->id);

                generate_node(code, node->r.expr);
                ins_byte(code, node->v.number);
                ins_byte(code, node->l.number);
                break;
            }
        case NODE_OPCODE_1:
            {
                debug(COMPILE, "generate [%d] NODE_OPCODE_1", node->id);

                ins_byte(code, node->v.number);
                ins_byte(code, node->l.number);
                break;
            }
        case NODE_RETURN:
            {
                debug(COMPILE, "generate [%d] NODE_RETURN", node->id);

                /* TODO: foreach */
                if (node->r.expr) {
                    generate_node(code, node->r.expr);
                    ins_byte(code, F_RETURN);
                }
                else {
                    ins_byte(code, F_RETURN_ZERO);
                }
                break;
            }
        case NODE_STRING:
            {
                debug(COMPILE, "generate [%d] NODE_STRING", node->id);

                /* TODO: optimize */
                ins_byte(code, F_STRING);
                ins_short(code, node->v.number);
                break;
            }
        case NODE_NUMBER:
            {
                debug(COMPILE, "generate [%d] NODE_NUMBER", node->id);

                /* TODO: optimize */
                ins_byte(code, F_NUMBER);
                ins_int(code, node->v.number);
                break;
            }
        case NODE_CALL_1:
            {
                debug(COMPILE, "generate [%d] NODE_CALL_1", node->id);

                generate_expr_list(code, node->r.expr);

                ins_byte(code, node->v.number);
                ins_short(code, node->l.number);
                ins_byte(code, (node->r.expr ? node->r.expr->kind : 0));
                break;
            }
        case NODE_TWO_VALUES:
            {
                debug(COMPILE, "generate [%d] NODE_TWO_VALUES", node->id);

                generate_node(code, node->l.expr);
                generate_node(code, node->r.expr);
                break;
            }
        case NODE_EFUN:
            {
                debug(COMPILE, "generate [%d] NODE_EFUN", node->id);

                generate_expr_list(code, node->r.expr);

                ins_byte(code, F_EFUNV);
                ins_byte(code, node->l.number);
                ins_byte(code, node->v.number & ~VOID_EFUN_NEED_RETURN);

                /* NOTE: efun with return type void won't push any value.
                   The flag is used to tell the parser to append a push instruction manually when generating code.
                   It may be turned off during insert_pop_value.
                 */
                if (node->v.number & VOID_EFUN_NEED_RETURN) {
                    ins_byte(code, F_CONST0);
                }
                break;
            }
    }
}

Code *AstNode_GenerateCode(AstNode *node)
{
    Code *code;

    code = (Code *)CO_MALLOC(sizeof(Code));
    code->c_start = CO_MALLOC(DEFAULT_CODE_SIZE);
    code->c_end = code->c_start;
    code->c_max = code->c_start + DEFAULT_CODE_SIZE;

    generate_node(code, node);

    return code;
}

void AstNode_FreeCode(Code *code)
{
    CO_FREE(code->c_start);
    CO_FREE(code);
}

